package org.oimwrapper.connectors;

import java.util.*;

/**
 * This is the basis for all lookup recon tasks.<p>
 *
 * This task expects the following parameters.
 * <ul>
 * <li><code>Lookup</code> The name of the lookup table that this task will populate with data from the
 *                     target system.</li>
 * <li><code>DeleteOldRecords</code> Setting this to <code>true</code> will enable checking for entries that
 *                               exist in the lookup tables but no longer exist on the target system.   
 *                               Entries not present will be removed in from the lookup table.   Setting 
 *                               this to <code>false</code> will disable this check.</li>
 * <li><code>EmailStatusTo</code> If present, this should be a comma separated list of email addresses to receive
 *                            a summary message upon completion of the job.</li>
 * <li><code>EmailErrorTo</code> If present, this should be a comma separated list of email addresses to receive
 *                           a notification email for each submit error ecounter during the job.</li>
 * <li><code>EmailFrom</code> The email address that notifications will for the form address.   If this field is not present, no
 *                            email notifications will be sent.</li>
 * <li><code>CleanText</code> Setting this to <code>true<code> will remove all non-printable characters from the 
 *                            data submitted, and setting this to <code>false</code> will leave the submitted data
 *                            as-is.</li>
 * <li><code>EncodeTransform</code> The string transform to apply to the encode attribute.   Possible values are
 *                                  <code>upper</code> which will convert the value to uppercase, <code>lower</code>
 *                                  which will convert the value to lowercase, or <code>none</code>, which will leave
 *                                  the value as-is.
 * <li><code>DecodeTransform</code> The string transform to apply to the decode attribute.   Possible values are
 *                                  <code>upper</code> which will convert the value to uppercase, <code>lower</code>
 *                                  which will convert the value to lowercase, or <code>none</code>, which will leave
 *                                  the value as-is.
 * </ul>
 */
public abstract class OIMReconLookupTaskBase extends OIMTaskBase {

	/**
	 * True if a stop has been requested for this task.
	 */
	private boolean stop;

	/**
	 * The name of the lookup code that this task will store it's results to.
	 */
	private String lookup;

	/**
	 * True if resources instances on OIM no longer present on the target system should be removed.
	 */
	private boolean deleteRecon;

	/**
	 * This method is called once per task, and should initialize and open a connection to the target system.
	 * 
	 * @exception Exception
	 */
	public abstract void openConnection() throws Exception;

	/**
	 * This method is called once per task, and should close the open connection to the target system.
	 *
	 * @exception Exception
	 */
	public abstract void closeConnection() throws Exception;

	/**
	 * This method is called once to retrieve a Map of records on the target system.   The key value of the Map
	 * will be used as the encode value, and the value associated with that key will be used as the decode value;
	 * 
	 * @return A Map of records on the target system.
	 * @exception Exception
	 */
	public abstract Map getRecords() throws Exception;

	/**
	 * The default constructor.
	 * 
	 * @param LogCategory The name of the category to log messages to.
	 */
	public OIMReconLookupTaskBase(String LogCategory) {
		super(LogCategory);
		stop = false;
	}

	/**
	 * This method is called by the scheduler to stop a running task instance.
	 * 
	 * @return This always returns <code>true</code>.
	 */
	public final boolean stop() {
		logger.debug("Entering reconLookupTaskBase.stop()");
		logger.warn(" ---- Stopping current task ---- ");
		stop = true;
		logger.debug("Exiting reconLookupTaskBase.stop()");
		return stop;
	}

	/**
	 * This method returns a boolean indicating whether or not a stop has been requested.
	 *
	 * @return <code>true</code> if a stop is requested, or <code>false</code> otherwise.
	 */
	public final boolean isStopped() {
		logger.debug("Entering reconLookupTaskBase.isStopped()");
		logger.debug("Exiting reconLookupTaskBase.isStopped()");
		return stop;
	}

	/**
	 * This method is called by the scheduler to execute the task instance.
	 * 
	 */
	public final void execute() {
		Date start = new Date();
		int recordCount = 0;
		int failedRecords = 0;
		logger.debug("Entering reconLookupTaskBase.execute()");
		try {
			Map sourceRecords = null;
			openConnection();
			try {
				sourceRecords = getRecords();
			} finally {
				closeConnection();
			}
			if (sourceRecords != null) {
				deleteRecon = getBooleanAttribute("DeleteOldRecords", false);
				lookup = getCriticalAttribute("Lookup");
				String encodeTransform = getDefaultAttribute("EncodeTransform", "none");
				String decodeTransform = getDefaultAttribute("DecodeTransform", "none");
				String emailStatusTo = getAttribute("EmailStatusTo");
				String emailErrorTo = getAttribute("EmailErrorTo");
				String emailFrom = getAttribute("EmailFrom");
				logger.info("---------------------------------------------------------");
				logger.info(" Begining Reconciliation For " + lookup);
				logger.info("---------------------------------------------------------");
				createLookup(lookup);
				if (getBooleanAttribute("CleanText", false)) {
					sourceRecords = cleanCharacters(sourceRecords);
				}
				Map localRecords = getLookupValues(lookup);
				Map transformedSourceRecords = new HashMap();
				for (Iterator iterator = sourceRecords.keySet().iterator(); !stop && iterator.hasNext(); recordCount++) {
					try {
						String key = (String) iterator.next();
						String transformedKey = key;
						if (encodeTransform.equalsIgnoreCase("upper")) {
							transformedKey = key.toUpperCase();
						} else if (encodeTransform.equalsIgnoreCase("lower")) {
							transformedKey = key.toLowerCase();
						}
						String newValue = (String) sourceRecords.get(key);
						String transformedValue = newValue;
						if (decodeTransform.equalsIgnoreCase("upper")) {
							transformedValue = newValue.toUpperCase();
						} else if (decodeTransform.equalsIgnoreCase("lower")) {
							transformedValue = newValue.toLowerCase();
						}
						if (localRecords.get(transformedKey) != null) {
							String oldValue = (String) localRecords.get(transformedKey);
							if (!oldValue.equals(transformedValue)) {
								updateLookupValue(transformedKey, transformedValue, lookup, true);
							}
						} else {
							addLookupValue(transformedKey, transformedValue, lookup);
						}
						transformedSourceRecords.put(transformedKey, transformedValue);
					} catch (Exception e) {
						OIMExceptionHandler.handleException(logger, e, getClass());
						failedRecords++;
						try {
							if (!isEmpty(emailErrorTo) && !isEmpty(emailFrom)) {
								String body = "An error occured processing the data update for " + lookup;
								body += " - " + e.getClass().getName() + " : " + e.toString();
								sendEmail("Reconcilation failure for " + lookup, body, emailFrom, emailErrorTo);
							}
						} catch (Exception f) {
							OIMExceptionHandler.handleException(logger, f, getClass());
						}
					}
				}
				for (Iterator iterator = localRecords.keySet().iterator(); !stop && deleteRecon && iterator.hasNext(); ) {
					try {
						String key = (String) iterator.next();
						if (transformedSourceRecords.get(key) == null) {
							removeLookupValue(key, lookup, true);
							recordCount++;
						}
					} catch (Exception e) {
						OIMExceptionHandler.handleException(logger, e, getClass());
						failedRecords++;
						try {
							if (!isEmpty(emailErrorTo) && !isEmpty(emailFrom)) {
								String body = "An error occured processing the data update for " + lookup;
								body += " - " + e.getClass().getName() + " : " + e.toString();
								sendEmail("Reconcilation failure for " + lookup, body, emailFrom, emailErrorTo);
							}
						} catch (Exception f) {
							OIMExceptionHandler.handleException(logger, f, getClass());
						}
					}
				}
				Date end = new Date();
				long seconds = (end.getTime() - start.getTime()) / 1000;
				double rate = 0.0;
				if (seconds > 0) {
					rate = recordCount / seconds;
				}
				long hours = seconds / 3600;
				seconds = seconds - (hours * 3600);
				long minutes = seconds / 60;
				seconds = seconds - (minutes * 60);
				String difference = Long.toString(hours) + ":";
				if (minutes < 10) {
					difference += "0";
				}
				difference += Long.toString(minutes) + ":";
				if (seconds < 10) {
					difference += "0";
				}
				difference += Long.toString(seconds);
				logger.info("---------------------------------------------------------");
				logger.info(" Completed Reconciliation For " + lookup);
				logger.info("");
				logger.info("   Processed Records: " + Integer.toString(recordCount));
				logger.info("      Failed Records: " + Integer.toString(failedRecords));
				logger.info("  Records per Second: " + Double.toString(rate));
				logger.info("        Elapsed Time: " + difference);
				logger.info("---------------------------------------------------------");
				if (!isEmpty(emailStatusTo) && !isEmpty(emailFrom)) {
					String body = " Completed Reconciliation For " + lookup + "\n\n";
					body += "   Processed Records: " + Integer.toString(recordCount) + "\n";
					body += "      Failed Records: " + Integer.toString(failedRecords) + "\n";
					body += "  Records per Second: " + Double.toString(rate) + "\n";
					body += "        Elapsed Time: " + difference + "\n";
					sendEmail("Reconcilation completed for " + lookup, body, emailFrom, emailStatusTo);
				}
			}
		} catch (Exception e) {
			OIMExceptionHandler.handleException(logger, e, getClass());
		}
		logger.debug("Exiting reconLookupTaskBase.execute()");
	}
	
	/**
	 * Disables delete recon.
	 */
	public final void disableDeleteRecon() {
		logger.debug("Entering reconLookupTaskBase.disableDeleteRecon()");
		deleteRecon = false;
		logger.debug("Entering reconLookupTaskBase.disableDeleteRecon()");
	}
}